package core;

import gitobject.Blob;
import gitobject.Commit;
import gitobject.GitObject;
import gitobject.Tree;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import repository.Config;
import repository.Index;
import repository.Repository;
import utility.Utility;

import java.io.IOException;
import java.nio.file.Path;
import java.util.*;

@Command(
        name = "commit"
)
/**
 * 实现了jit commit命令，作用为提交add后的文件
 * 形式为jit commit -"消息内容"
 */
public class JitCommit implements Runnable {
    @Option(names = {"-m", "--message"}, description = "Commit Message")
    String msg;

    @Override
    public void run() {
        var repo = Repository.getInstance();
        if (!repo.exist()) {
            System.err.println("Not a jit repo.");
            System.exit(-1);
        }
        if (msg == null) {
            System.err.println("You need give message");
            System.exit(-1);
        }
        if (!hasCommit()) {
            System.err.println("Noting to commit.");
        }
        try {
            var index = repo.getIndex();
            Tree tree = index.buildTree();
            tree.write();
            Config config = repo.getConfig();
            var branch = repo.getCurrentBranch();
            Commit commit = new Commit.Builder()
                    .parent(branch == null ? null : branch.getCommit())
                    .author(String.format("%s<%s>", config.getUserName(), config.getUserEmail()))
                    .committer(String.format("%s<%s>", config.getUserName(), config.getUserEmail()))
                    .message(msg)
                    .tree(tree.getKey())
                    .time(new Date())
                    .build();
            commit.write();
            repo.getCurrentBranch().setCommit(commit.getKey());
        } catch (
                Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 检查是否commit
     * @return 返回true或false
     */
    private boolean hasCommit() {
        var repo = Repository.getInstance();
        // 检查是否有新的添加
        var branch = repo.getCurrentBranch();
        if (branch.getCommit() == null) {
            return true;
        }
        try {
            var index = repo.getIndex();
            if(index.getStage().isEmpty()){
                return false;
            }
            Map<Path, Index.Entry> stage = new HashMap<>();
            stage.putAll(index.getStage());
            Commit commit = (Commit) GitObject.findByKey(branch.getCommit());
            Tree tree = (Tree) GitObject.findByKey(commit.getTree());
            Queue<Tree> treeQueue = new ArrayDeque<>();
            treeQueue.add(tree);
            // 检查是否有修改
            boolean changed = false;
            while (!treeQueue.isEmpty()) {
                Tree cur = treeQueue.poll();
                for (GitObject obj : cur.getObjectTree()) {
                    switch (obj.getType()) {
                        case Blob : {
                            Blob blob = (Blob) obj;
                            if (stage.containsKey(blob.getPath())) {
                                if (blob.getLastModified() != stage.get(blob.getPath()).getLast_access_time()) {
                                    changed = true;
                                } else {
                                    stage.remove(blob.getPath());
                                }
                            } else {
                                changed = true;
                            }
                            break;
                        }
                        case Tree : {
                            treeQueue.add((Tree) obj);
                            break;
                        }
                        default : {
                            System.err.println("Invalid Object Type! " + Utility.hex2string(obj.getKey()));
                        }
                    }
                    if (changed) {
                        break;
                    }
                }
            }
            if (!changed && !stage.isEmpty()) {
                changed = true;
            }
            return changed;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
